We end this document with a program that demonstrates how to use the QuickTime effects architecture and apply it to a character in an animation scene.
The code in this section shows how you can build a transition effect and apply it to a character in a realistic animation of a UFO encounter! In the program, a fading effect is applied to transition to the spaceship image. The image fades in and out as per the rate and the number of frames set for the transition.
It shows the usage of effects and effects presenters. The kCrossFadeTransitionType effect is applied to the source and the destination images, which that makes them fade as per the number of frames set for the transition.
The QTEffectPresenter is used to embed the QTTransition effect and present it to the Compositor , which draws it to the canvas. Note that the QTTransition effect cannot be directly added to the Compositor ; instead, it is given to the QTEffectPresenter , which is added to the Compositor . If a filter were applied, it would have the same limitations as a transition when added to a Compositor . It must be added using the QTEffectPresenter .
A ripple effect is applied to the water image (in front of the water image taking up the same location), using the CompositableEffect class. Zero sourced effects, such as the ripple effect, can be added directly to a Compositor .
CompositableEffect ce = new CompositableEffect ();
AtomContainer effectSample = new AtomContainer();
effectSample.insertChild (new Atom(kParentAtomIsContainer),
kEffectWhatAtom,
1,
0,
EndianOrder.flipNativeToBigEndian32(kWaterRippleCodecType));
ce.setEffect (effectSample);
ce.setDisplayBounds (new QDRect(0, 220, 300, 80));
comp.addMember (ce, 3);
The Fader class is used to create the QTTransition and return the QTEffectPresenter that will supply the pixel data that becomes the image data for this member's sprite:
class Fader implements StdQTConstants {
Fader() throws Exception {
File file = QTFactory.findAbsolutePath ("pics/Ship.pct");
QTFile f = new QTFile (file.getAbsolutePath());
QDGraphics g = new QDGraphics (new QDRect (78, 29));
g.setBackColor (QDColor.black);
g.eraseRect(null);
ImagePresenter srcImage = ImagePresenter.fromGWorld(g);
Compositable destImage = new GraphicsImporterDrawer (f);
ef = new QTTransition ();
ef.setRedrawing(true);
ef.setSourceImage (srcImage);
ef.setDestinationImage (destImage);
ef.setDisplayBounds (new QDRect(78, 29));
ef.setEffect (createFadeEffect (kEffectBlendMode, kCrossFadeTransitionType));
ef.setFrames(60);
ef.setCurrentFrame(0);
}
private QTTransition ef;
public QTEffectPresenter makePresenter() throws QTException {
QTEffectPresenter efPresenter = new QTEffectPresenter (ef);
return efPresenter;
}
public QTTransition getTransition () {
return ef;
}
AtomContainer createFadeEffect (int effectType, int effectNumber)
throws QTException {
AtomContainer effectSample = new AtomContainer();
effectSample.insertChild (new Atom(kParentAtomIsContainer),
kEffectWhatAtom,
1,
0,
EndianOrder.flipNativeToBigEndian32(kCrossFadeTransitionType));
effectSample.insertChild (new Atom(kParentAtomIsContainer),
effectType,
1,
0,
EndianOrder.flipNativeToBigEndian32(effectNumber));
return effectSample;
}
}
We then create the QTEffectPresenter for the transition and add it as a member of the Compositor :
Fader fader = new Fader();
QTEffectPresenter efp = fader.makePresenter();
efp.setGraphicsMode (new GraphicsMode (blend, QDColor.gray));
efp.setLocation(80, 80);
comp.addMember (efp, 1);
comp.addController(new TransitionControl (20, 1, fader.getTransition()));
The controller object implements the TicklishController and subclasses the PeriodicAction class that has a doAction() method, which gets invoked on every tickle call.
class TransitionControl extends PeriodicAction
implements TicklishController {
TransitionControl (int scale, int period, QTTransition t) {
super (scale , period);
this.t = t;
}
The doAction() call is overidden to set the current frame and redraw the TransitionEffect . The source and the destination images of the transition effect are swapped when the number of set frames is reached. The transition's controller then rests for a few seconds before it is awakened again and reapplied. The incoming time values to the doAction method (called by PeriodicAction.tickle() ) are used to calculate the rest and transition, ensuring that if the rate of playback changes, the transition controller will react to these changes.
When the transition is quiescent, we set the redrawing state of the QTEffectPresenter to false . This ensures that when the Compositor invalidates this presenter it will not invalidate the sprite, as we are not currently drawing into the QTEffectPresenter . When the transition is being applied, that is when the current frame is set, then the isRedrawing method will return true . The Invalidator for the QTEffectPresenter will then redraw the effect and invalidate its sprite presenter. Thus, this controller is also able to control the redrawing of both itself and its sprite through the use of the redrawing state and ensure that the Compositor only renders the sprite that presents this QTEffectPresenter when it actually changes its pixel data--that is, the image data of the effect's presenter.
protected void doAction (float er, int tm) throws QTException { if (waiting) { if ((er > 0 && ((startWaitTime + waitForMsecs) <= tm)) || (er < 0 && ((startWaitTime - waitForMsecs) >= tm))) { waiting = false; t.setRedrawing(true); } else return; } int curr_frm = t.getCurrentFrame(); curr_frm++; t.setCurrentFrame(curr_frm); if (curr_frm > t.getFrames()) { curr_frm = 0; t.setRedrawing(false); t.setCurrentFrame(curr_frm); t.swapImages(); waiting = true; startWaitTime = tm; }
| Previous | Chapter Top | Chapter Contents |